#include "Box.h"

float smallest_positive_valid(int num, float *arr, vec3 p, vec3 d, vec3 min,
        vec3 max) {
    int i;
    float m = -1;
    vec3 val;

    for (i = 0; i < num; i++) {
        val = p + arr[i] * d;
        if (val.x < min.x - 0.001 || val.x > max.x + 0.001 || 
            val.y < min.y - 0.001 || val.y > max.y + 0.001 ||
            val.z < min.z - 0.001 || val.z > max.z + 0.001) {
            continue;
        }
        if (m == -1 && arr[i] > 0.f) {
            m = arr[i];
        }
        else if (arr[i] > 0.f && arr[i] < m) {
            m = arr[i];
        }
    }

    return m;
}

int smallest_index(int num, float *arr) {
    int i;
    float min = arr[0];
    int minRet = 0;

    for (i = 1; i < num; i++) {
        if (arr[i] < min) {
            min = arr[i];
            minRet = i;
        }
    }

    return minRet;
}

Symbol WBox::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);
    numFill = parseVector(file, curToken, &curToken, 3);
    min = vec3(numFill[0], numFill[1], numFill[2]);
    free(numFill);

    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    max = vec3(numFill[0], numFill[1], numFill[2]);
    free(numFill);

    curToken = parseMaterials(file, curToken, this);

    if (min.x > max.x) {
        max.x += min.x;
        min.x = max.x - min.x;
        max.x = max.x - min.x;
    }

    if (min.y > max.y) {
        max.y += min.y;
        min.y = max.y - min.y;
        max.y = max.y - min.y;
    }

    if (min.z > max.z) {
        max.z += min.z;
        min.z = max.z - min.z;
        max.z = max.z - min.z;
    }

    return curToken;
}

bool WBox::intersect(Ray *r) {
    vec4 pPrime, dPrime;
    vec3 p, d;
    float gmin, gmax;
    float xmin = FLT_MIN, xmax = FLT_MAX, 
          ymin = FLT_MIN, ymax = FLT_MAX, 
          zmin = FLT_MIN, zmax = FLT_MAX;

    float arr[6];
    float t;


    pPrime = transform * vec4(r->point.x, r->point.y, r->point.z, 1.0);
    dPrime = transform * vec4(r->d.x, r->d.y, r->d.z, 0.0);
    p = vec3(pPrime.x, pPrime.y, pPrime.z);
    d = vec3(dPrime.x, dPrime.y, dPrime.z);


    if ((p.x > max.x && d.x > 0 ||
        p.x < min.x && d.x < 0) &&
        (p.y > max.y && d.y > 0 ||
        p.y < min.y && d.y < 0) &&
        (p.z > max.z && d.z > 0 ||
        p.z < min.z && d.z < 0)) {
        return false;
    }

        xmax = (max.x - p.x) / d.x;
        ymax = (max.y - p.y) / d.y;
        zmax = (max.z - p.z) / d.z;
        xmin = (min.x - p.x) / d.x;
        ymin = (min.y - p.y) / d.y;
        zmin = (min.z - p.z) / d.z;

    if (xmax < xmin) {
        /* two variable swap. keeps us in 2 registers
         * this should be a safe operation (no overflow)
         * because FLT_MAX is absolutely ginormous 
         */
        xmax = xmax + xmin;
        xmin = xmax - xmin;
        xmax = xmax - xmin;
    }

    if (ymax < ymin) {
        ymax = ymax + ymin;
        ymin = ymax - ymin;
        ymax = ymax - ymin;
    }

    if (zmax < zmin) {
        zmax = zmax + zmin;
        zmin = zmax - zmin;
        zmax = zmax - zmin;
    }

    gmax = std::min(std::min(xmax, ymax), zmax);
    gmin = std::max(std::max(xmin, ymin), zmin);

    if (gmin < gmax) {
        arr[0] = xmin;
        arr[1] = xmax;
        arr[2] = ymin;
        arr[3] = ymax;
        arr[4] = zmin;
        arr[5] = zmax;
        

        gmax = smallest_positive_valid(6, arr, p, d, min, max);
        if (gmax > 0.001 && gmax < r->t) {
            r->t = gmax;
            return true;
        }
    }
    
    return false;
}

vec3 WBox::genRandomPoint(vec3 look, int cur, int lim) {
    vec4 point;

    point.x = min.x - (min.x - max.x) * ((float) rand()) / RAND_MAX;
    point.y = min.y - (min.y - max.y) * ((float) rand()) / RAND_MAX;
    point.z = min.z - (min.z - max.z) * ((float) rand()) / RAND_MAX;
    point.w = 1.0;

    point = transpose(transform) * point;

    return vec3(point.x, point.y, point.z);
}

vec3 WBox::getNormal(Ray *r) {
    vec3 p = r->point + r->t * r->d;
    vec3 boxPoint;
    vec3 d;
    vec4 dPrime, pPrime;
    vec4 n;
    float x, y;
    float arr[6];
    int i;

    pPrime = transform * vec4(r->point.x, r->point.y, r->point.z, 1.0);
    dPrime = transform * vec4(r->d.x, r->d.y, r->d.z, 0.0);
    p = vec3(pPrime.x, pPrime.y, pPrime.z);
    d = vec3(dPrime.x, dPrime.y, dPrime.z);
    p = p + r->t * d;

    /* dxmin */
    arr[0] = abs(p.x - min.x);
    /* dxmax */
    arr[1] = abs(p.x - max.x);
    /* dymin */
    arr[2] = abs(p.y - min.y);
    /* dymax */
    arr[3] = abs(p.y - max.y);
    /* dzmin */
    arr[4] = abs(p.z - min.z);
    /* dzmax */
    arr[5] = abs(p.z - max.z);

    i = smallest_index(6, arr);

    switch(i) {
        case 0:
            n = vec4(-1.0f, 0.f, 0.f, 0.f);
            boxPoint = p - min;
            x = boxPoint.z;
            y = boxPoint.y;
            break;
        case 1:
            n = vec4(1.f, 0.f, 0.f, 0.f);
            boxPoint = max - p;
            x = max.z - boxPoint.z;
            y = max.y - boxPoint.y;
            break;
        case 2:
            boxPoint = p - min;
            x = boxPoint.x;
            y = boxPoint.z;
            n = vec4(0.f, -1.f, 0.f, 0.f);
            break;
        case 3:
            boxPoint = p - min;
            x = boxPoint.x;
            y = boxPoint.z;
            n = vec4(0.f, 1.f, 0.f, 0.f);
            break;
        case 4:
            boxPoint = p - min;
            x = boxPoint.x;
            y = boxPoint.y;
            n = vec4(0.f, 0.f, -1.f, 0.f);
            break;
        case 5:
            boxPoint = p - min;
            x = boxPoint.x;
            y = boxPoint.y;
            n = vec4(0.f, 0.f, 1.f, 0.f);
            break;
        default:
            x = 0;
            y = 0;
            n = vec4(0.0);
            break;
    }

    n = transpose(transform) * n;
    switch ((int) finish.bump) {
        case 0:
            boxPoint = vec3(0);
            break;
        case 1:
            boxPoint = perlin1(x * 100, y * 100, p, noise);
            break;
        case 2:
            boxPoint = perlin2(x * 100, y * 100, p, noise);
            break;
        case 3:
            boxPoint = perlin3(x * 100, y * 100, p, noise);
            break;
        default:
            boxPoint = vec3(0);
            break;
    }

    return normalize(vec3(n.x, n.y, n.z) + boxPoint);
}              

AABB *WBox::generateAABB() {
    AABB *ret;

    ret = new AABB();
    ret->g = this;
    ret->isLeaf = true;

    ret->min = min;
    ret->max = max;

    fix_AABB_transform(ret, vtrans);

    return ret;
}
